﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace ImageTour
{
    struct Float3
    {
        public float x, y, z;
        public Float3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Float3 Scale(float u)
        {
            return new Float3(x * u, y * u, z * u);
        }
        public PointF ToPointF()
        {
            return new PointF(x, y);
        }
        public Point ToPoint()
        {
            return new Point((int)x, (int)y);
        }
        public float Length()
        {
            return (float)Math.Sqrt(x * x + y * y + z * z);
        }
        public static Float3 operator -(Float3 v1, Float3 v2)
        {
            Float3 rs;
            rs.x = v1.x - v2.x;
            rs.y = v1.y - v2.y;
            rs.z = v1.z - v2.z;
            return rs;
        }
        public static Float3 operator +(Float3 v1, Float3 v2)
        {
            Float3 rs;
            rs.x = v1.x + v2.x;
            rs.y = v1.y + v2.y;
            rs.z = v1.z + v2.z;
            return rs;
        }
        public static float operator *(Float3 v1, Float3 v2)
        {
            return v1.x * v2.x + v1.y*v2.y + v1.z * v2.z;
        }

        internal Float3 Normalize()
        {
            float s = 1.0f / Length();
            return Scale(s);
        }

        internal static Float3 Cross(Float3 v1, Float3 v2)
        {
            return new Float3(v1.y * v2.z - v1.z * v2.y,
                v1.z * v2.x - v1.x * v2.z,
                v1.x * v2.y - v1.y * v2.x);
        }
    }
    struct Float2
    {
        public float x, y;
        public Float2(float x, float y)
        {
            this.x = x;
            this.y = y;
        }

        public Float2(Point point)
        {
            x = (float)point.X;
            y = (float)point.Y;
        }

        public Float2(PointF point)
        {
            x = point.X;
            y = point.Y;
        }

        public Float2 Scale(float u)
        {
            return new Float2(x * u, y * u);
        }
        public PointF ToPointF()
        {
            return new PointF(x, y);
        }
        public Point ToPoint()
        {
            return new Point((int)x, (int)y);
        }
        public float Length()
        {
            return (float)Math.Sqrt(x * x + y * y);
        }
        public static Float2 operator -(Float2 v1, Float2 v2)
        {
            Float2 rs;
            rs.x = v1.x - v2.x;
            rs.y = v1.y - v2.y;
            return rs;
        }
        public static Float2 operator +(Float2 v1, Float2 v2)
        {
            Float2 rs;
            rs.x = v1.x + v2.x;
            rs.y = v1.y + v2.y;
            return rs;
        }
        public static float operator *(Float2 v1, Float2 v2)
        {
            return v1.x * v2.x + v1.y*v2.y;
        }

        internal Float2 Scale(Float2 s)
        {
            return new Float2(x * s.x, y * s.y);
        }
    }

    class Matrix4
    {
        public float[] M;

        public Matrix4()
        {
            M = new float[16];
        }

        internal static Matrix4 Perspective(float fovy, float aspect, float zNear, float zFar)
        {
            double xmin, xmax, ymin, ymax;
            ymax = zNear * Math.Tan(fovy / 2.0f);
            ymin = -ymax;
            xmin = ymin * aspect;
            xmax = ymax * aspect;
            return Frustum((float)xmin, (float)xmax, (float)ymin, (float)ymax, zNear, zFar);
        }

        internal static Matrix4 Frustum(float left, float right, float bottom, float top, float near, float far)
        {
            float A = (right + left) / (right - left);
            float B = (top + bottom) / (top - bottom);
            float C = -(far + near) / (far - near);
            float D = -2 * far * near / (far - near);
            Matrix4 rs = new Matrix4();
            rs.M[0] = 2*near/(right-left); rs.M[1] = 0.0f; rs.M[2] = 0.0f; rs.M[3] = 0.0f;
            rs.M[4] = 0.0f; rs.M[5] = 2*near/(top-bottom); rs.M[6] = 0.0f; rs.M[7] = 0.0f;
            rs.M[8] = A; rs.M[9] = B; rs.M[10] = C; rs.M[11] = -1.0f;
            rs.M[12] = 0.0f; rs.M[13] = 0.0f; rs.M[14] = D; rs.M[15] = 0.0f;
            return rs;
        }

        internal static Matrix4 Translate(float x, float y, float z)
        {
            Matrix4 rs = new Matrix4();
            rs.M[0] = 1.0f; rs.M[1] = 0.0f; rs.M[2] = 0.0f; rs.M[3] = 0.0f;
            rs.M[4] = 0.0f; rs.M[5] = 1.0f; rs.M[6] = 0.0f; rs.M[7] = 0.0f;
            rs.M[8] = 0.0f; rs.M[9] = 0.0f; rs.M[10] = 1.0f; rs.M[11] = 0.0f;
            rs.M[12] = x; rs.M[13] = y; rs.M[14] = z; rs.M[15] = 1.0f;
            return rs;
        }

        public static Matrix4 operator *(Matrix4 M1, Matrix4 M2)
        {
            Matrix4 rs = new Matrix4();
            for (int i = 0; i < 4; i++) //col
            {
                for (int j = 0; j < 4; j++) // row
                {
                    rs.M[i * 4 + j] = M1.M[0 * 4 + j] * M2.M[i * 4 + 0] + M1.M[1 * 4 + j] * M2.M[i * 4 + 1] + M1.M[2 * 4 + j] * M2.M[i*4+2]
                        + M1.M[3 * 4 + j] * M2.M[i * 4 + 3];
                }
            }
            return rs;
        }

        internal Float3 Transform(Float3 p)
        {
            Float3 rs;
            rs.x = M[0] * p.x + M[4] * p.y + M[8] * p.z + M[12];
            rs.y = M[1] * p.x + M[5] * p.y + M[9] * p.z + M[13];
            rs.z = M[2] * p.x + M[6] * p.y + M[10] * p.z + M[14];
            float w = 1.0f / (M[3] * p.x + M[7] * p.y + M[11] * p.z + M[15]);
            return rs.Scale(w);
        }
    }
}
